package com.yingyi.user.service.impl;

import com.yingyi.basic.constant.RegisterMethod;
import com.yingyi.basic.constant.Identity;
import com.yingyi.basic.exception.BusinessException;
import com.yingyi.basic.service.impl.BaseServiceImpl;
import com.yingyi.basic.util.Md5Utils;
import com.yingyi.basic.util.StrUtils;
import com.yingyi.user.domain.Logininfo;
import com.yingyi.user.domain.User;
import com.yingyi.user.dto.PhoneRegisterDto;
import com.yingyi.user.mapper.LogininfoMapper;
import com.yingyi.user.mapper.UserMapper;
import com.yingyi.user.service.IUserService;


import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * (t_user)表服务层实现类
 *
 * @author Yingyi
 * @since 2022-07-26 17:16:27
 */
@Service
public class UserServiceImpl extends BaseServiceImpl<User> implements IUserService {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private LogininfoMapper logininfoMapper;

    @Autowired
    private RedisTemplate redisTemplate;
    
    /**
     * user中salt字段的处理
     * @param user
     */
    private void encryptPassword(User user) {
        String salt = null; 
        if(user.getSalt() == null || user.getSalt().isEmpty()) {
            salt = StrUtils.getComplexRandomString(32);
            user.setSalt(salt);
        } else {
            salt = user.getSalt();
        }
        
        String pwdEncrpted = Md5Utils.encrypByMd5(salt.concat(user.getPassword()));
        user.setPassword(pwdEncrpted);
    }
    /**
     * 添加记录到t_user中同步更新到 t_logininfo中
     * @param user
     */
    @Override
    @Transactional
    public void insert(User user) {
        //salt、password字段的处理
        encryptPassword(user);

        Logininfo logininfo = user2LoginInfo(user);

        logininfoMapper.insert(logininfo);

        //获取logininfo的自增长主键赋值给t_user中关联的外键字段logininfo_id
        user.setLogininfoId(logininfo.getId());

        userMapper.insert(user);
    }


    @Override
    @Transactional
    public void update(User user) {

        encryptPassword(user);

        //先更新t_logininfo表中对应的记录
        Logininfo logininfo = user2LoginInfo(user);
        logininfo.setId(user.getLogininfoId());
        logininfoMapper.update(logininfo);

        //再更新t_user中的记录

        userMapper.update(user);
    }

    @Override
    @Transactional
    public void delete(Long id) {

        User user = userMapper.queryById(id);

        logininfoMapper.delete(user.getLogininfoId());

        userMapper.delete(id);
    }


    @Override
    @Transactional
    public void patchDelete(List<Long> ids) {
        ArrayList<User> users = new ArrayList<>();

        for (Long id : ids) {
            User user = userMapper.queryById(id);
            users.add(user);
        }

        if(users.isEmpty()) {
            return;
        }

        List<Long> logininfoIds = users.stream().map(user -> user.getLogininfoId()).collect(Collectors.toList());

        logininfoMapper.patchDelete(logininfoIds);

        userMapper.patchDelete(ids);
    }

    private Logininfo user2LoginInfo(User user) {
        Logininfo logininfo = new Logininfo();

        /**
         * 相同字段的赋值
         */
        BeanUtils.copyProperties(user, logininfo);

        /**
         * type disable 赋值
         */
        logininfo.setType(Identity.USER);
        logininfo.setDisable(user.getState() == 0);

        return logininfo;
    }

    /**
     * 通过手机号注册
     * @param dto
     */
    @Override
    public void registerByPhone(PhoneRegisterDto dto) throws BusinessException {
        //1 参数校验 非空校验 手机号是否已注册、 短信验证码是否过期、是否正确 两次输入的密码相同校验
        String phone = dto.getPhone();
        String password = dto.getPassword();
        String confirmPassword = dto.getConfirmPassword();
        String phoneCode = dto.getPhoneCode();

        if(StringUtils.isEmpty(phone)
            || StringUtils.isEmpty(password)
            || StringUtils.isEmpty(confirmPassword)
            || StringUtils.isEmpty(phoneCode)){

            throw new BusinessException("参数为空，请检查后重试");
        }

        Object codeInRedis = redisTemplate.opsForValue().get(phone.concat(RegisterMethod.PHONE_REGISTER));
        if(codeInRedis == null){
            throw new BusinessException("验证码已失效，请重新获取");
        }
        String code = codeInRedis.toString().split(":")[0];
        if(!code.equalsIgnoreCase(phoneCode)){
            throw new BusinessException("验证码不正确，请重新输入");
        }


        User dbUser = userMapper.queryByPhone(phone);
        if(dbUser != null){
            //手机号已注册
            throw new BusinessException("手机号已注册，请直接登录");
        }


        if(!password.equals(confirmPassword)){
            throw new BusinessException("两次输入的密码不一致，请检查后重试");
        }

        //2 数据存储 因为要使用logininfo来做登录信息验证，则需要将用户信息同步更新到 user和logininfo表中

        //数据准备
        Logininfo logininfo = dto2loginfo(dto);
        User user = loginfo2user(logininfo);

        //存储logininfo, 成功后logininfo会包含自增的主键
        logininfoMapper.insert(logininfo);

        //user中含有一个关联logininfo的外键 logininfo_id
        user.setLogininfoId(logininfo.getId());
        //存储user
        userMapper.insert(user);

    }

    /**
     * 退出登录，清除redis中的保存的用户的登录信息
     * @param token
     */
    @Override
    public void logout(String token) {
        if(token != null && !token.isEmpty()){
            redisTemplate.delete(token);
        }
    }

    /**
     * 将Logininfo对象转换为User对象
     * @param logininfo
     * @return
     */
    private User loginfo2user(Logininfo logininfo) {
        User user = new User();

        BeanUtils.copyProperties(logininfo, user);
        return user;
    }

    /**
     * 将PhoneRegister转换为Logininfo对象
     * @param dto
     * @return
     */
    private Logininfo dto2loginfo(PhoneRegisterDto dto) {
        Logininfo logininfo = new Logininfo();
        logininfo.setPhone(dto.getPhone());
        logininfo.setType(Identity.USER);

        //salt
        String salt = StrUtils.getComplexRandomString(32);
        logininfo.setSalt(salt);

        //password  encrypt with salt+password
        logininfo.setPassword(Md5Utils.encrypByMd5(salt.concat(dto.getPassword())));

        return logininfo;
    }

}
